﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using CSJ2K.Util;
using CSJ2K.Util.IO;
using CSJ2K.Util.Collections.Generic;

namespace CSJ2K.Jpc
{
    public class Packet
    {
        public CodeStream CodeStream;
        public Tile Tile;
        public TilePart TilePart;

        public int Layer;
        public int Resolution;
        public int Component;
        public int Precinct;

        public long Start;

        public bool NonzeroLengthPacket;

        public RasterDictionary<CodeBlock>[] codeBlock = new RasterDictionary<CodeBlock>[4];

        public Packet(TilePart tilePart, Stream stream, int layer, int resolution, int component, int precinct)
            : this(tilePart, new BitstreamReader(stream), layer, resolution, component, precinct) { }
        public Packet(TilePart tilePart, BitstreamReader bitStream, int layer, int resolution, int component, int precinct)
        {
            TilePart = tilePart;
            CodeStream = tilePart.CodeStream;
            Tile = tilePart.Tile;

            Layer = layer;
            Resolution = resolution;
            Component = component;
            Precinct = precinct;

            Start = bitStream.Source.Position;

            int padding = 0;

            // Don't go any further if this packet is zero length
            if ((NonzeroLengthPacket = bitStream.ReadBit()) == true)
            {
                if (DEBUG.Packet) Debug.WriteLine("NonzeroLengthPacket: " + NonzeroLengthPacket + " (0x" + bitStream.Buffer[1].ToString("X2") + " 0x" + bitStream.Buffer[0].ToString("X2") + ")");

                // Horizontal and vertical position of tile
                uint tileIndexX = tilePart.Sot.I % CodeStream.numXtiles;
                uint tileIndexY = tilePart.Sot.I / CodeStream.numXtiles;

                // Tile upper-left(0) and lower-right(1) coordinate
                uint tileX0 = Math.Max(CodeStream.Siz.XTO + (tileIndexX * CodeStream.Siz.XT), CodeStream.Siz.XO);
                uint tileY0 = Math.Max(CodeStream.Siz.YTO + (tileIndexY * CodeStream.Siz.YT), CodeStream.Siz.YO);
                uint tileX1 = Math.Min(CodeStream.Siz.XTO + ((tileIndexX + 1) * CodeStream.Siz.XT), CodeStream.Siz.X);
                uint tileY1 = Math.Min(CodeStream.Siz.YTO + ((tileIndexY + 1) * CodeStream.Siz.YT), CodeStream.Siz.Y);

                // Tile coordinates within the domain of the component
                uint tileComponentX0 = (tileX0 + CodeStream.Siz.Component[component].XR - 1) / CodeStream.Siz.Component[component].XR;
                uint tileComponentY0 = (tileY0 + CodeStream.Siz.Component[component].YR - 1) / CodeStream.Siz.Component[component].YR;
                uint tileComponentX1 = (tileX1 + CodeStream.Siz.Component[component].XR - 1) / CodeStream.Siz.Component[component].XR;
                uint tileComponentY1 = (tileY1 + CodeStream.Siz.Component[component].YR - 1) / CodeStream.Siz.Component[component].YR;

                // divisor for the current resolution
                uint resolutionDivisor = (uint)Math.Pow(2, (CodeStream.Resolutions - (uint)resolution));
                // used to calculate the subband coordinates.
                uint subbandDivisor = (uint)Math.Pow(2, (CodeStream.Resolutions - (uint)resolution - 1));

                /*
                if (DEBUG.Packet)
                {
                    uint tileX2 = CodeStream.Siz.XTO + ((tileIndexX + 1) * CodeStream.Siz.XT);
                    uint tileY2 = CodeStream.Siz.YTO + ((tileIndexY + 1) * CodeStream.Siz.YT);

                    uint tileWidth = tileX1 - tileX0;
                    uint tileHeight = tileY1 - tileY0;
                    uint tileNormalWidth = tileX2 - tileX0;
                    uint tileNormalHeight = tileY2 - tileY0;
                    Debug.WriteLine("Tile size: " + tileWidth + ", " + tileHeight);
                    Debug.WriteLine("Normal tile size: " + tileNormalWidth + ", " + tileNormalHeight);

                    uint tileComponentX2 = (tileX2 + CodeStream.Siz.Component[component].XR - 1) / CodeStream.Siz.Component[component].XR;
                    uint tileComponentY2 = (tileY2 + CodeStream.Siz.Component[component].YR - 1) / CodeStream.Siz.Component[component].YR;

                    uint tileComponentWidth = tileComponentX1 - tileComponentX0;
                    uint tileComponentHeight = tileComponentY1 - tileComponentY0;
                    uint tileComponentNormalWidth = tileComponentX2 - tileComponentX0;
                    uint tileComponentNormalHeight = tileComponentY2 - tileComponentY0;
                    Debug.WriteLine("Tile-component size: " + tileComponentWidth + ", " + tileComponentHeight);
                    Debug.WriteLine("Normal tile-component size: " + tileComponentNormalWidth + ", " + tileComponentNormalHeight);

                    // Tile-component's coordinates for the resolution level
                    uint tileResolutionX0 = (tileComponentX0 + resolutionDivisor - 1) / resolutionDivisor;
                    uint tileResolutionY0 = (tileComponentY0 + resolutionDivisor - 1) / resolutionDivisor;
                    uint tileResolutionX1 = (tileComponentX1 + resolutionDivisor - 1) / resolutionDivisor;
                    uint tileResolutionY1 = (tileComponentY1 + resolutionDivisor - 1) / resolutionDivisor;
                    uint tileResolutionX2 = (tileComponentX2 + resolutionDivisor - 1) / resolutionDivisor;
                    uint tileResolutionY2 = (tileComponentY2 + resolutionDivisor - 1) / resolutionDivisor;

                    uint tileResolutionWidth = tileResolutionX1 - tileResolutionX0;
                    uint tileResolutionHeight = tileResolutionY1 - tileResolutionY0;
                    uint tileResolutionNormalWidth = tileResolutionX2 - tileResolutionX0;
                    uint tileResolutionNormalHeight = tileResolutionY2 - tileResolutionY0;
                    if (DEBUG.Packet) Debug.WriteLine("Tile-component size for this resolution level: " + tileResolutionWidth + ", " + tileResolutionHeight);
                    if (DEBUG.Packet) Debug.WriteLine("Normal tile-component size for this resolution level: " + tileResolutionNormalWidth + ", " + tileResolutionNormalHeight);
                }
                */

                long[] subbandBlocks = new long[4];

                // for each subband (LL if Resolution = 0 or HL, LH and HH for Resolution > 0)
                int numSubBands = (resolution == 0) ? 1 : 3;
                int firstSubBand = (resolution == 0) ? 0 : 1;
                for (int subband = firstSubBand; subband < firstSubBand + numSubBands; subband++)
                {
                    int subbandX = subband % 2;
                    int subbandY = subband / 2;

                    if (DEBUG.Packet) Debug.WriteLine("Subband " + (subband == 0 ? "LL" : (subband == 1 ? "HL" : (subband == 2 ? "LH" : "HH"))) + " [" + subbandX + ", " + subbandY + "]");
                    if (DEBUG.Packet) Debug.Indent();

                    uint tileSubbandX0 = ((tileComponentX0 - (subbandDivisor * (uint)subbandX)) + resolutionDivisor - 1) / resolutionDivisor;
                    uint tileSubbandY0 = ((tileComponentY0 - (subbandDivisor * (uint)subbandY)) + resolutionDivisor - 1) / resolutionDivisor;
                    uint tileSubbandX1 = ((tileComponentX1 - (subbandDivisor * (uint)subbandX)) + resolutionDivisor - 1) / resolutionDivisor;
                    uint tileSubbandY1 = ((tileComponentY1 - (subbandDivisor * (uint)subbandY)) + resolutionDivisor - 1) / resolutionDivisor;

                    uint subbandWidth = tileSubbandX1 - tileSubbandX0;
                    uint subbandHeight = tileSubbandY1 - tileSubbandY0;
                    if (DEBUG.Packet) Debug.WriteLine("Subband size: " + subbandWidth + ", " + subbandHeight);

                    // Code-block dimensions specific to this tile, component, resolution level and subband
                    uint codeBlockWidth = (uint)Math.Pow(2, 2 + CodeStream.Cod.SP.E); // Code-block width
                    uint codeBlockHeight = (uint)Math.Pow(2, 2 + CodeStream.Cod.SP.F); // Code-block height
                    uint codeBlocksWide = (subbandWidth + codeBlockWidth - 1) / codeBlockWidth;     // Number of Code-blocks wide - Ceiling(Xt/Xcb)
                    uint codeBlocksHigh = (subbandHeight + codeBlockHeight - 1) / codeBlockHeight;  // Number of Code-blocks high - Ceiling(Yt/Ycb)
                    uint numCodeBlocks = codeBlocksWide * codeBlocksHigh;
                    if (DEBUG.Packet) Debug.WriteLine("Number of code-blocks: " + codeBlocksWide + ", " + codeBlocksHigh + " (" + numCodeBlocks + ")");

                    subbandBlocks[subband] = numCodeBlocks;

                    // Allocate space for codeblocks in this subband.
                    codeBlock[subband] = new RasterDictionary<CodeBlock>(codeBlocksWide);

                    // for all code-blocks in this sub-band confined to the relevant precinct, in raster order
                    for (int y = 0; y < codeBlocksHigh; y++)
                    {
                        for (int x = 0; x < codeBlocksWide; x++)
                        {
                            codeBlock[subband][x, y] = new CodeBlock(this, bitStream, subband, codeBlocksWide, codeBlocksHigh, numCodeBlocks, x, y);
                        }
                    }
                    if (DEBUG.Packet) Debug.Unindent();
                }

                padding = bitStream.Align();
                if (DEBUG.Packet) Debug.WriteLine("Padding: " + padding);

                for (int S = firstSubBand; S < firstSubBand + numSubBands; S++)
                {
                    for (int block = 0; block < subbandBlocks[S]; block++)
                    {
                        long x = block % codeBlock[S].Width;
                        long y = block / codeBlock[S].Width;

                        codeBlock[S][x, y].ReadCodeBlockData(bitStream);
                    }
                }
            }
            else
            {
                if (DEBUG.Packet) Debug.WriteLine("NonzeroLengthPacket: " + NonzeroLengthPacket);
                padding = bitStream.Align();
                if (DEBUG.Packet) Debug.WriteLine("Padding: " + padding);
            }
        }
    }
}
